// © 2022 and later: Unicode, Inc. and others.
// License & terms of use: https://www.unicode.org/copyright.html

package com.ibm.icu.dev.test.message2;

import com.ibm.icu.dev.test.CoreTestFmwk;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.CurrencyAmount;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Trying to show off most of the features in one place.
 *
 * <p>It covers the examples in the <a
 * href="https://github.com/unicode-org/message-format-wg/blob/main/spec/syntax.md">spec
 * document</a>, except for the custom formatters ones, which are too verbose and were moved to
 * separate test classes.
 */
@RunWith(JUnit4.class)
@SuppressWarnings({"static-method", "javadoc"})
public class Mf2FeaturesTest extends CoreTestFmwk {

    // November 23, 2022 at 7:42:37.123 PM
    static final Date TEST_DATE = new Date(1669261357123L);

    @Test
    public void testEmptyMessage() {
        TestUtils.runTestCase(
                new TestCase.Builder().pattern("").arguments(Args.NONE).expected("").build());
    }

    @Test
    public void testPlainText() {
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Hello World!")
                        .arguments(Args.NONE)
                        .expected("Hello World!")
                        .build());
    }

    @Test
    public void testPlaceholders() {
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Hello, {$userName}!")
                        .arguments(Args.of("userName", "John"))
                        .expected("Hello, John!")
                        .build());
    }

    @Test
    public void testArgumentMissing() {
        // Test to check what happens if an argument name from the placeholder is not found
        // We do what the old ICU4J MessageFormat does.
        String message =
                "Hello {$name}, today is {$today :datetime dateFields=year-month-day-weekday dateLength=long}.";

        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(Args.of("name", "John", "today", TEST_DATE))
                        .expected("Hello John, today is Wednesday, November 23, 2022.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(Args.of("name", "John"))
                        .expected("Hello John, today is {$today}.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(Args.of("today", TEST_DATE))
                        .expected("Hello {$name}, today is Wednesday, November 23, 2022.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(Args.NONE)
                        .expected("Hello {$name}, today is {$today}.")
                        .build());
    }

    @Test
    public void testDefaultLocale() {
        String message = "Date: {$date :date fields=year-month-day-weekday length=long}.";
        String expectedEn = "Date: Wednesday, November 23, 2022.";
        String expectedRo = "Date: miercuri, 23 noiembrie 2022.";
        Map<String, Object> arguments = Args.of("date", TEST_DATE);

        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(arguments)
                        .expected(expectedEn)
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(arguments)
                        .locale("ro")
                        .expected(expectedRo)
                        .build());

        Locale originalLocale = Locale.getDefault();
        Locale.setDefault(Locale.forLanguageTag("ro"));
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(arguments)
                        .locale("en-US")
                        .expected(expectedEn)
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(arguments)
                        .expected(expectedRo)
                        .build());
        Locale.setDefault(originalLocale);
    }

    @Test
    public void testAllKindOfDates() {
        // Default function
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Testing date formatting: {$date}.")
                        .locale("ro")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: mie., 23 nov. 2022, 19:42.")
                        .build());
        // Default options
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Testing date formatting: {$date :datetime}.")
                        .locale("ro")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: mie., 23 nov. 2022, 19:42.")
                        .build());

        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(
                                "Testing date formatting: {$date :date fields=year-month-day length=long}.")
                        .locale("ro-RO")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: 23 noiembrie 2022.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Testing date formatting: {$date :datetime timePrecision=minute}.")
                        .locale("ro-RO")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: 19:42.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(
                                "Testing date formatting: {$date :date fields=year-month-day length=medium}.")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: Nov 23, 2022.")
                        .build());

        // Skeleton
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(
                                "Testing date formatting: {$date :datetime icu:skeleton=yMMMdjms}.")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: Nov 23, 2022, 7:42:37\u202FPM.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Testing date formatting: {$date :datetime timePrecision=minute}.")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: 7:42\u202FPM.")
                        .build());

        // Style
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(
                                "Testing date formatting: {$date :datetime dateFields=year-month-day dateLength=long}.")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: November 23, 2022.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(
                                "Testing date formatting: {$date :datetime dateFields=year-month-day dateLength=medium}.")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: Nov 23, 2022.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(
                                "Testing date formatting: {$date :datetime dateFields=year-month-day dateLength=short}.")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: 11/23/2022.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(
                                "Testing date formatting: {$date :datetime timePrecision=second timeZoneStyle=short}.")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: 7:42:37\u202FPM PST.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Testing date formatting: {$date :datetime timePrecision=second}.")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: 7:42:37\u202FPM.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Testing date formatting: {$date :datetime timePrecision=minute}.")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: 7:42\u202FPM.")
                        .build());
    }

    @Test
    public void testAllKindOfNumbers() {
        double value = 1234567890.97531;

        // From literal values
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("From literal: {123456789 :number}!")
                        .locale("ro")
                        .arguments(Args.of("val", value))
                        .expected("From literal: 123.456.789!")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("From literal: {|123456789.531| :number}!")
                        .locale("ro")
                        .arguments(Args.of("val", value))
                        .expected("From literal: 123.456.789,531!")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("From literal: {123456789.531 :number}!")
                        .locale("my")
                        .arguments(Args.of("val", value))
                        .expected(
                                "From literal: \u1041\u1042\u1043,\u1044\u1045\u1046,\u1047\u1048\u1049.\u1045\u1043\u1041!")
                        .build());

        // Testing that the detection works for various types (without specifying :number)
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Default double: {$val}!")
                        .locale("en-IN")
                        .arguments(Args.of("val", value))
                        .expected("Default double: 1,23,45,67,890.97531!")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Default double: {$val}!")
                        .locale("ro")
                        .arguments(Args.of("val", value))
                        .expected("Default double: 1.234.567.890,97531!")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Default float: {$val}!")
                        .locale("ro")
                        .arguments(Args.of("val", 3.1415926535))
                        .expected("Default float: 3,141593!")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Default long: {$val}!")
                        .locale("ro")
                        .arguments(Args.of("val", 1234567890123456789L))
                        .expected("Default long: 1.234.567.890.123.456.789!")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Default number: {$val}!")
                        .locale("ro")
                        .arguments(Args.of("val", new BigDecimal("1234567890123456789.987654321")))
                        .expected("Default number: 1.234.567.890.123.456.789,987654!")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Price: {$val}")
                        .locale("de")
                        .arguments(
                                Args.of(
                                        "val",
                                        new CurrencyAmount(1234.56, Currency.getInstance("EUR"))))
                        .expected("Price: 1.234,56\u00A0\u20AC")
                        .build());

        // Various skeletons
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Option, minFraction: {$val :number minimumFractionDigits=8}!")
                        .locale("ro")
                        .arguments(Args.of("val", value))
                        .expected("Option, minFraction: 1.234.567.890,97531000!")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Option, maxFraction: {$val :number maximumFractionDigits=3}!")
                        .locale("ro")
                        .arguments(Args.of("val", value))
                        .expected("Option, maxFraction: 1.234.567.890,975!")
                        .build());
        // Currency
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Skeletons, currency: {$val :number icu:skeleton=|currency/EUR|}!")
                        .locale("de")
                        .arguments(Args.of("val", value))
                        .expected("Skeletons, currency: 1.234.567.890,98\u00A0\u20AC!")
                        .build());
        // Currency as a parameter
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Skeletons, currency: {$val :number icu:skeleton=$skel}!")
                        .locale("de")
                        .arguments(Args.of("val", value, "skel", "currency/EUR"))
                        .expected("Skeletons, currency: 1.234.567.890,98\u00A0\u20AC!")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Skeletons, currency: {$val :number icu:skeleton=$skel}!")
                        .locale("de")
                        .arguments(Args.of("val", value, "skel", "currency/JPY"))
                        .expected("Skeletons, currency: 1.234.567.891\u00A0\u00A5!")
                        .build());

        // Various measures
        double celsius = 27;
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(
                                ""
                                        + ".local $intl = {$valC :number icu:skeleton=|unit/celsius|}\n"
                                        + ".local $us = {$valF :number icu:skeleton=|unit/fahrenheit|}\n"
                                        + "{{Temperature: {$intl} ({$us})}}")
                        .locale("ro")
                        .arguments(Args.of("valC", celsius, "valF", celsius * 9 / 5 + 32))
                        .expected("Temperature: 27 \u00B0C (80,6 \u00B0F)")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Height: {$len :number icu:skeleton=|unit/meter|}")
                        .locale("ro")
                        .arguments(Args.of("len", 1.75))
                        .expected("Height: 1,75 m")
                        .build());

        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Skeletons, currency: {$val :number icu:skeleton=|currency/EUR|}!")
                        .locale("de")
                        .arguments(Args.of("val", value))
                        .expected("Skeletons, currency: 1.234.567.890,98\u00A0\u20AC!")
                        .build());
    }

    @Test
    public void testSpecialPluralWithDecimals() {
        String message;
        message =
                ".local $amount = {$count :number}\n"
                        + ".match $amount\n"
                        + "  1 {{I have {$amount} dollar.}}\n"
                        + "  * {{I have {$amount} dollars.}}";
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .locale("en-US")
                        .arguments(Args.of("count", 1))
                        .expected("I have 1 dollar.")
                        .build());
        message =
                ".local $amount = {$count :number minimumFractionDigits=2}\n"
                        + ".match $amount\n"
                        + "  one {{I have {$amount} dollar.}}\n"
                        + "  *   {{I have {$amount} dollars.}}";
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .locale("en-US")
                        .arguments(Args.of("count", 1))
                        .expected("I have 1.00 dollars.")
                        .build());
    }

    @Test
    public void testDefaultFunctionAndOptions() {
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Testing date formatting: {$date}.")
                        .locale("ro")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: mie., 23 nov. 2022, 19:42.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern("Testing date formatting: {$date :datetime}.")
                        .locale("ro")
                        .arguments(Args.of("date", TEST_DATE))
                        .expected("Testing date formatting: mie., 23 nov. 2022, 19:42.")
                        .build());
    }

    @Test
    public void testSimpleSelection() {
        String message =
                ".input {$count :number}\n"
                        + ".match $count\n"
                        + " 1 {{You have one notification.}}\n"
                        + " * {{You have {$count} notifications.}}";

        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(Args.of("count", 1))
                        .expected("You have one notification.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(Args.of("count", 42))
                        .expected("You have 42 notifications.")
                        .build());
    }

    @Test
    public void testComplexSelection() {
        String message =
                ""
                        + ".input {$photoCount :number}\n"
                        + ".input {$userGender :string}\n"
                        + ".match $photoCount $userGender\n"
                        + " 1 masculine {{{$userName} added a new photo to his album.}}\n"
                        + " 1 feminine {{{$userName} added a new photo to her album.}}\n"
                        + " 1 * {{{$userName} added a new photo to their album.}}\n"
                        + " * masculine {{{$userName} added {$photoCount} photos to his album.}}\n"
                        + " * feminine {{{$userName} added {$photoCount} photos to her album.}}\n"
                        + " * * {{{$userName} added {$photoCount} photos to their album.}}";

        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(
                                Args.of(
                                        "photoCount",
                                        1,
                                        "userGender",
                                        "masculine",
                                        "userName",
                                        "John"))
                        .expected("John added a new photo to his album.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(
                                Args.of(
                                        "photoCount",
                                        1,
                                        "userGender",
                                        "feminine",
                                        "userName",
                                        "Anna"))
                        .expected("Anna added a new photo to her album.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(
                                Args.of(
                                        "photoCount",
                                        1,
                                        "userGender",
                                        "unknown",
                                        "userName",
                                        "Anonymous"))
                        .expected("Anonymous added a new photo to their album.")
                        .build());

        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(
                                Args.of(
                                        "photoCount",
                                        13,
                                        "userGender",
                                        "masculine",
                                        "userName",
                                        "John"))
                        .expected("John added 13 photos to his album.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(
                                Args.of(
                                        "photoCount",
                                        13,
                                        "userGender",
                                        "feminine",
                                        "userName",
                                        "Anna"))
                        .expected("Anna added 13 photos to her album.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(
                                Args.of(
                                        "photoCount",
                                        13,
                                        "userGender",
                                        "unknown",
                                        "userName",
                                        "Anonymous"))
                        .expected("Anonymous added 13 photos to their album.")
                        .build());
    }

    // Local Variables

    @Test
    public void testSimpleLocaleVariable() {
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(
                                ""
                                        + ".local $exp = {$expDate :datetime dateFields=year-month-day-weekday dateLength=short}\n"
                                        + "{{Your tickets expire on {$exp}.}}")
                        .arguments(Args.of("count", 1, "expDate", TEST_DATE))
                        .expected("Your tickets expire on We, 11/23/2022.")
                        .build());
    }

    @Test
    public void testLocaleVariableWithSelect() {
        String message =
                ""
                        + ".input {$count :number}\n"
                        + ".local $exp = {$expDate :date year=numeric month=short day=numeric weekday=short}\n"
                        + ".match $count\n"
                        + " 1 {{Your ticket expires on {$exp}.}}\n"
                        + " * {{Your {$count} tickets expire on {$exp}.}}";

        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(Args.of("count", 1, "expDate", TEST_DATE))
                        .expected("Your ticket expires on Wed, Nov 23, 2022.")
                        .build());
        TestUtils.runTestCase(
                new TestCase.Builder()
                        .pattern(message)
                        .arguments(Args.of("count", 3, "expDate", TEST_DATE))
                        .expected("Your 3 tickets expire on Wed, Nov 23, 2022.")
                        .build());
    }
}
